require 'fileutils'

module Cert
  class Runner
    def launch
      run

      installed = FastlaneCore::CertChecker.installed?(ENV["CER_FILE_PATH"])
      UI.message "Verifying the certificate is properly installed locally..."
      UI.user_error!("Could not find the newly generated certificate installed", show_github_issues: true) unless installed
      UI.success "Successfully installed certificate #{ENV['CER_CERTIFICATE_ID']}"
      return ENV["CER_FILE_PATH"]
    end

    def login
      UI.message "Starting login with user '#{Cert.config[:username]}'"
      Spaceship.login(Cert.config[:username], nil)
      Spaceship.select_team
      UI.message "Successfully logged in"
    end

    def run
      FileUtils.mkdir_p(Cert.config[:output_path])

      FastlaneCore::PrintTable.print_values(config: Cert.config, hide_keys: [:output_path], title: "Summary for cert #{Fastlane::VERSION}")

      login

      should_create = Cert.config[:force]
      unless should_create
        cert_path = find_existing_cert
        should_create = cert_path.nil?
      end

      return unless should_create

      if create_certificate # no certificate here, creating a new one
        return # success
      else
        UI.user_error!("Something went wrong when trying to create a new certificate...")
      end
    end

    # Command method for the :revoke_expired sub-command
    def revoke_expired_certs!
      FastlaneCore::PrintTable.print_values(config: Cert.config, hide_keys: [:output_path], title: "Summary for cert #{Fastlane::VERSION}")

      login

      to_revoke = expired_certs

      if to_revoke.empty?
        UI.success "No expired certificates were found to revoke! 👍"
        return
      end

      revoke_count = 0

      to_revoke.each do |certificate|
        begin
          UI.message "#{certificate.id} #{certificate.name} has expired, revoking..."
          certificate.revoke!
          revoke_count += 1
        rescue => e
          UI.error "An error occurred while revoking #{certificate.id} #{certificate.name}"
          UI.error "#{e.message}\n#{e.backtrace.join("\n")}" if FastlaneCore::Globals.verbose?
        end
      end

      UI.success "#{revoke_count} expired certificate#{'s' if revoke_count != 1} #{revoke_count == 1 ? 'has' : 'have'} been revoked! 👍"
    end

    def expired_certs
      certificates.select do |certificate|
        certificate.expires < Time.now.utc
      end
    end

    def find_existing_cert
      certificates.each do |certificate|
        unless certificate.can_download
          next
        end

        path = store_certificate(certificate)
        private_key_path = File.expand_path(File.join(Cert.config[:output_path], "#{certificate.id}.p12"))

        if FastlaneCore::CertChecker.installed?(path)
          # This certificate is installed on the local machine
          ENV["CER_CERTIFICATE_ID"] = certificate.id
          ENV["CER_FILE_PATH"] = path

          UI.success "Found the certificate #{certificate.id} (#{certificate.name}) which is installed on the local machine. Using this one."

          return path
        elsif File.exist?(private_key_path)
          keychain = File.expand_path(Cert.config[:keychain_path])
          password = Cert.config[:keychain_password]
          FastlaneCore::KeychainImporter.import_file(private_key_path, keychain, keychain_password: password)
          FastlaneCore::KeychainImporter.import_file(path, keychain, keychain_password: password)

          ENV["CER_CERTIFICATE_ID"] = certificate.id
          ENV["CER_FILE_PATH"] = path

          UI.success "Found the cached certificate #{certificate.id} (#{certificate.name}). Using this one."

          return path
        else
          UI.error "Certificate #{certificate.id} (#{certificate.name}) can't be found on your local computer"
        end

        File.delete(path) # as apparantly this certificate is pretty useless without a private key
      end

      UI.important "Couldn't find an existing certificate... creating a new one"
      return nil
    end

    # All certificates of this type
    def certificates
      certificate_type.all
    end

    # The kind of certificate we're interested in
    def certificate_type
      case Cert.config[:platform].to_s
      when 'ios', 'tvos'
        cert_type = Spaceship.certificate.production
        cert_type = Spaceship.certificate.in_house if Spaceship.client.in_house?
        cert_type = Spaceship.certificate.development if Cert.config[:development]

      when 'macos'
        cert_type = Spaceship.certificate.mac_app_distribution
        cert_type = Spaceship.certificate.mac_development if Cert.config[:development]

      end

      cert_type
    end

    def create_certificate
      # Create a new certificate signing request
      csr, pkey = Spaceship.certificate.create_certificate_signing_request

      # Use the signing request to create a new distribution certificate
      begin
        certificate = certificate_type.create!(csr: csr)
      rescue => ex
        if ex.to_s.include?("You already have a current")
          type_name = (Cert.config[:development] ? "Development" : "Distribution")
          UI.user_error!("Could not create another #{type_name} certificate, reached the maximum number of available #{type_name} certificates.", show_github_issues: true)
        end

        raise ex
      end

      # Store all that onto the filesystem

      request_path = File.expand_path(File.join(Cert.config[:output_path], "#{certificate.id}.certSigningRequest"))
      File.write(request_path, csr.to_pem)

      private_key_path = File.expand_path(File.join(Cert.config[:output_path], "#{certificate.id}.p12"))
      File.write(private_key_path, pkey)

      cert_path = store_certificate(certificate)

      # Import all the things into the Keychain
      keychain = File.expand_path(Cert.config[:keychain_path])
      password = Cert.config[:keychain_password]
      FastlaneCore::KeychainImporter.import_file(private_key_path, keychain, keychain_password: password)
      FastlaneCore::KeychainImporter.import_file(cert_path, keychain, keychain_password: password)

      # Environment variables for the fastlane action
      ENV["CER_CERTIFICATE_ID"] = certificate.id
      ENV["CER_FILE_PATH"] = cert_path

      UI.success "Successfully generated #{certificate.id} which was imported to the local machine."

      return cert_path
    end

    def store_certificate(certificate)
      path = File.expand_path(File.join(Cert.config[:output_path], "#{certificate.id}.cer"))
      raw_data = certificate.download_raw
      File.write(path, raw_data)
      return path
    end
  end
end
